package com.yumeng.framework.auth.security.authentication.custom;

import com.yumeng.common.helper.AssertHelper;
import com.yumeng.framework.auth.exception.SecurityAuthException;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.cache.NullUserCache;

/**
 * 自定义AuthenticationProvider
 * 参考AbstractUserDetailsAuthenticationProvider
 *
 * @author wxd
 * @date 2025/4/11 11:10
 */
@Getter
@Setter
@Slf4j
public abstract class CustomAbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean {

    private UserCache userCache = new NullUserCache();
    private boolean forcePrincipalAsString = false;
    protected boolean hideUserNotFoundExceptions = true;
    private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();
    private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();
    private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();


    @Override
    public final void afterPropertiesSet() throws Exception {
        AssertHelper.assertNotNull(this.userCache, "A user cache must be set");
        AssertHelper.assertNotNull(this.preAuthenticationChecks, "A pre authentication checks must be set");
        AssertHelper.assertNotNull(this.postAuthenticationChecks, "A post authentication checks must be set");
        doAfterPropertiesSet();
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        if (!(authentication instanceof CustomLoginAbstractAuthenticationToken)){
            throw new SecurityAuthException("CustomAbstractUserDetailsAuthenticationProvider 仅支持CustomLoginAbstractAuthenticationToken类型token");
        }
        //Object principal = authentication.getPrincipal();
        //Object credentials = authentication.getCredentials();
        String username = determineUsername(authentication);

        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;
            user = doRetrieveUser(username, (CustomLoginAbstractAuthenticationToken)authentication);
        }
        try {
            this.preAuthenticationChecks.check(user);
            additionalAuthenticationChecks(user, (CustomLoginAbstractAuthenticationToken)authentication);
        }
        catch (AuthenticationException ex) {
            if (!cacheWasUsed) {
                throw ex;
            }
            //如果使用了缓存，且认证失败，则重新获取user（非缓存）
            cacheWasUsed = false;
            user = doRetrieveUser(username, (CustomLoginAbstractAuthenticationToken)authentication);
            this.preAuthenticationChecks.check(user);
            additionalAuthenticationChecks(user, (CustomLoginAbstractAuthenticationToken)authentication);
        }
        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }
        Object principalToReturn = user;
        if (this.forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }
        return createSuccessAuthentication(principalToReturn, authentication, user);
    }

    private UserDetails doRetrieveUser(String username, CustomLoginAbstractAuthenticationToken authentication) throws AuthenticationException{
        UserDetails user = null;
        try {
            user = retrieveUser(username, authentication);
        }
        catch (UsernameNotFoundException ex) {
            log.debug("UserDetails 没有被找到: {}", username);
            if (!this.hideUserNotFoundExceptions) {
                throw ex;
            }
            throw new BadCredentialsException("用户认证信息缺失");
        }catch (InternalAuthenticationServiceException ex) {
            throw ex;
        }catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
        if (user == null) {
            throw new InternalAuthenticationServiceException("未获取到有效用户（UserDetails）");
        }
        return user;
    }

    private String determineUsername(Authentication authentication) {
        return (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
    }

    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
                                                         UserDetails user) {
        CustomLoginAbstractAuthenticationToken result = createSuccessAuthenticationToken(principal, authentication, user);
        result.setDetails(authentication.getDetails());
        log.debug("Authenticated user");
        return result;
    }

    protected void doAfterPropertiesSet() throws Exception {}
    protected abstract UserDetails retrieveUser(String username, CustomLoginAbstractAuthenticationToken authentication) throws AuthenticationException;
    protected abstract void additionalAuthenticationChecks(UserDetails userDetails, CustomLoginAbstractAuthenticationToken authentication) throws AuthenticationException;
    protected abstract CustomLoginAbstractAuthenticationToken createSuccessAuthenticationToken(Object principal, Authentication authentication, UserDetails user);


    @Override
    public boolean supports(Class<?> authentication) {
        //需通过子类覆盖
        return false;
    }

    private static class DefaultPreAuthenticationChecks implements UserDetailsChecker {

        @Override
        public void check(UserDetails user) {
            if (!user.isAccountNonLocked()) {
                String msg = "Failed to authenticate since user account is locked";
                log.warn(msg);
                throw new LockedException(msg);
            }
            if (!user.isEnabled()) {
                String msg = "Failed to authenticate since user account is disabled";
                log.warn(msg);
                throw new DisabledException(msg);
            }
            if (!user.isAccountNonExpired()) {
                String msg = "Failed to authenticate since user account has expired";
                log.warn(msg);
                throw new AccountExpiredException(msg);
            }
        }

    }

    private static class DefaultPostAuthenticationChecks implements UserDetailsChecker {

        @Override
        public void check(UserDetails user) {
            if (!user.isCredentialsNonExpired()) {
                String msg = "Failed to authenticate since user account credentials have expired";
                log.warn(msg);
                throw new CredentialsExpiredException(msg);
            }
        }

    }
}
